package pl.elka.simulator.core.mobilityManager.impl;

import java.util.ArrayList;
import java.util.List;

import pl.elka.simulator.core.AreaConstraint;
import pl.elka.simulator.core.Constraint;
import pl.elka.simulator.core.EnviromentConfiguration;
import pl.elka.simulator.core.IPaintable;
import pl.elka.simulator.core.Node;
import pl.elka.simulator.core.Position;
import pl.elka.simulator.core.configurationCommand.ConfigurationCommandType;
import pl.elka.simulator.core.configurationCommand.ables.IAbleToLeaderConfiguration;
import pl.elka.simulator.core.experiment.NodePath;
import pl.elka.simulator.core.mobilityManager.IMobilityManager;
import pl.elka.simulator.core.mobilityManager.MobilityManagerType;
import pl.elka.simulator.core.mobilityManager.impl.nodes.LeaderNode;
import pl.elka.simulator.core.mobilityManager.impl.nodes.RWMNode;

public class ReferencePointGroupMobilityManager extends RandomWalkMobilityManager implements IAbleToLeaderConfiguration{

	private String id;
	private List<LeaderNode> leaders = new ArrayList<LeaderNode>();
	private List<LeaderNode> leadersBuffer = new ArrayList<LeaderNode>(); 
	private final float LEADER_RANGE = 0.2f;//2 metry 
	
	protected List<RWMNode> nodes = new ArrayList<RWMNode>();	
	
	@Override
	public List<LeaderNode> getLeaders() {		
		return leaders;
	}

	@Override
	public void setLeaders(List<LeaderNode> nodes) {
		this.leaders = nodes;
	}

	@Override
	public IMobilityManager newInstance() {
		return new ReferencePointGroupMobilityManager();
	}

	@Override
	public MobilityManagerType getType() {	
		return  MobilityManagerType.ReferencePointGroupMobility;
	}

	@Override
	public List<ConfigurationCommandType> getAllowedConfigurationCommands() {
		List<ConfigurationCommandType> ret = new ArrayList<ConfigurationCommandType>();	
		ret.add(ConfigurationCommandType.AddLeaderConfigurationCommand);
		ret.add(ConfigurationCommandType.DeleteLeaderConfigurationCommand);
		return ret;
	}

	@Override
	public void makeMove(long elapsedTime) {
		moveLeaders(elapsedTime);		
		moveNodes(elapsedTime);
	}

	private void moveNodes(long elapsedTime) {
		for(RWMNode node:nodes){			
			
			if(node.lastVelocityChangeInterval>=node.velocityChangeInterval){
				node.lastVelocityChangeInterval = 0;				
				
				LeaderNode nearestLeader = findNearestLeader(node);
				Position targetPosition = randomPosition(nearestLeader.position, LEADER_RANGE);
				
				Position velocity = new Position(targetPosition.x - node.position.x,targetPosition.y - node.position.y);
				velocity.setMagnitude(Node.MAX_VELOCITY);
								
				node.currentVelocity = velocity;
			}	
			
			Position newPosition = new Position(
					node.position.x + node.currentVelocity.x*elapsedTime,
					node.position.y+node.currentVelocity.y*elapsedTime
				);
			
			Position collisionPosition = areaConstraint.findCollisionPoint(node.position, newPosition);
			if(collisionPosition==null){
				for(Constraint constraint:constraints){
					collisionPosition = constraint.findCollisionPoint(node.position, newPosition);
					if(collisionPosition!=null){					
						break;
					}
				}
			}
			
			//jesli wpadl na ograniczenie, to ustawiam go przy nim i wymuszam zniame predkosci
			if(collisionPosition!=null){
				node.position = collisionPosition;
				node.lastVelocityChangeInterval=node.velocityChangeInterval;				
			}	
			//jesli nie wpadl, to po prostu go przestawiam
			else{
				node.position = newPosition;
				node.lastVelocityChangeInterval+=elapsedTime;
			}																	
						
			node.path.addPathElement(node.position.clone());
		}
	}
	
	private Position randomPosition(Position center, float distance){
		float rx = (float) Math.random()*2*distance;
		float ry = (float) Math.random()*2*distance;
		
		rx-=distance;
		ry-=distance;
						
		return new Position(center.x + rx,center.y + ry);
	}

	private LeaderNode findNearestLeader(RWMNode node) {
		float nearestDist = Float.MAX_VALUE;
		float cDist = 0;
		LeaderNode nearestLeader = null;
		
		for(LeaderNode l:leadersBuffer){
			cDist = (l.position.x-node.position.x)*(l.position.x-node.position.x) + (l.position.y-node.position.y)*(l.position.y-node.position.y);
			if(cDist<nearestDist){
				nearestLeader = l;
				nearestDist = cDist;
			}
		}
		return nearestLeader;
	}

	private void moveLeaders(long elapsedTime) {
		for(LeaderNode leader:leadersBuffer){
			//wyznaczam nowa pozycje wezla
			Position newPosition = new Position(
					leader.position.x + leader.currentVelocity.x*elapsedTime,
					leader.position.y+leader.currentVelocity.y*elapsedTime
					);
			
			//sprawdzam, czy wezel, przy pokonywaniu drogi nie wpadnie na ograniczenie
			Position collisionPosition = areaConstraint.findCollisionPoint(leader.position, newPosition);
			if(collisionPosition==null){
				for(Constraint constraint:constraints){
					collisionPosition = constraint.findCollisionPoint(leader.position, newPosition);
					if(collisionPosition!=null){					
						break;
					}
				}
			}
			
			//jesli wpasl na ograniczenie, to ustawiam go przy nim i wymuszam zniame predkosci
			if(collisionPosition!=null){
				leader.position = collisionPosition;
				leader.lastVelocityChangeInterval=leader.velocityChangeInterval;
				//System.out.println("kolizja");
			}	
			//jesli nie wpadl, to po prostu go przestawiam
			else{
				leader.position = newPosition;
				leader.lastVelocityChangeInterval+=elapsedTime;
			}
			
			if(leader.lastVelocityChangeInterval>=leader.velocityChangeInterval){
				leader.lastVelocityChangeInterval = 0;				
				leader.currentVelocity = randomVelocity(leader);
			}
			leader.path.addPathElement(leader.position.clone());
			
		}
	}

	@Override
	public void setId(String id) {
		this.id = id;
	}

	@Override
	public String getId() {
		return id;
	}

	@Override
	public List<IPaintable> getPaintaibleElements() {			
		List<IPaintable> ret = new ArrayList<IPaintable>();
		ret.addAll(leaders);
				
		List<? extends Node> nodes = getAllNodes();
		
		for(Node n:nodes){
			ret.add(n.getPath());
		}
		
		return ret;
	}

	@Override
	public void applyEnviroment(EnviromentConfiguration cfg) {
		leadersBuffer = new ArrayList<LeaderNode>();
		for(LeaderNode n : leaders){
			n.setVelocityChangeInterval(50/*RandomWalkMobilityManager.MAX_CHANGE_VELOCITY_INTERVAL*2*/);
			n.setCurrentVelocity(randomVelocity(n));	
			LeaderNode newLN = new LeaderNode(n);
			newLN.path = new NodePath(n);
			newLN.path.addPathElement(newLN.position.clone());
			
			
			leadersBuffer.add(newLN);
		}	
		
		nodes = new ArrayList<RWMNode>();		
		for(Node n : cfg.getNodes()){
			int vChangeInterval = randomInterval(n);
			RWMNode rwmNode = new RWMNode(vChangeInterval, vChangeInterval, randomVelocity(n), (Position)n.getPosition().clone());
			nodes.add(rwmNode);
			rwmNode.path.addPathElement(rwmNode.position.clone());
		}	
		
		constraints = cfg.getConstraints();
		areaConstraint = cfg.getAreaConstraint();
	}
	protected int randomInterval(Node n){
		return 1;//2*MIN_CHANGE_VELOCITY_INTERVAL;
	}
	private List<? extends Node> getAllNodes() {
		List<Node> ret = new ArrayList<Node>(nodes);
		ret.addAll(leadersBuffer);
		return ret;
	}
	@Override
	public List<RWMNode> getNodes() {
		return nodes;
	}

}
